/////////////////////////////////////////////////////////////////////////////////////////////////
//
//  Tencent is pleased to support the open source community by making tgfx available.
//
//  Copyright (C) 2025 Tencent. All rights reserved.
//
//  Licensed under the BSD 3-Clause License (the "License"); you may not use this file except
//  in compliance with the License. You may obtain a copy of the License at
//
//      https://opensource.org/licenses/BSD-3-Clause
//
//  unless required by applicable law or agreed to in writing, software distributed under the
//  license is distributed on an "as is" basis, without warranties or conditions of any kind,
//  either express or implied. see the license for the specific language governing permissions
//  and limitations under the license.
//
/////////////////////////////////////////////////////////////////////////////////////////////////

#include "GLCommandQueue.h"
#include "GLTexture.h"
#include "core/utils/PixelFormatUtil.h"
#include "gpu/opengl/GLBuffer.h"
#include "gpu/opengl/GLGPU.h"
#include "gpu/opengl/GLSemaphore.h"
#include "gpu/opengl/GLUtil.h"
#include "tgfx/core/Buffer.h"

namespace tgfx {
void GLCommandQueue::writeBuffer(std::shared_ptr<GPUBuffer> buffer, size_t bufferOffset,
                                 const void* data, size_t size) {
  if (data == nullptr || size == 0) {
    LOGE("GLCommandQueue::writeBuffer() data is null or size is zero!");
    return;
  }
  if (bufferOffset + size > buffer->size()) {
    LOGE("GLCommandQueue::writeBuffer() size exceeds buffer size!");
    return;
  }
  auto gl = gpu->functions();
  auto glBuffer = std::static_pointer_cast<GLBuffer>(buffer);
  auto target = GLBuffer::GetTarget(glBuffer->usage());
  DEBUG_ASSERT(target != 0);
  gl->bindBuffer(target, glBuffer->bufferID());
  gl->bufferSubData(target, static_cast<GLintptr>(bufferOffset), static_cast<GLsizeiptr>(size),
                    data);
}

void GLCommandQueue::writeTexture(std::shared_ptr<Texture> texture, const Rect& rect,
                                  const void* pixels, size_t rowBytes) {
  if (texture == nullptr || rect.isEmpty() || !(texture->usage() & TextureUsage::TEXTURE_BINDING)) {
    return;
  }
  auto gl = gpu->functions();
  auto caps = gpu->caps();
  if (caps->flushBeforeWritePixels) {
    gl->flush();
  }
  auto glTexture = static_cast<GLTexture*>(texture.get());
  auto state = gpu->state();
  state->bindTexture(glTexture);
  const auto& textureFormat = caps->getTextureFormat(glTexture->format());
  auto bytesPerPixel = PixelFormatBytesPerPixel(glTexture->format());
  gl->pixelStorei(GL_UNPACK_ALIGNMENT, static_cast<int>(bytesPerPixel));
  int x = static_cast<int>(rect.x());
  int y = static_cast<int>(rect.y());
  int width = static_cast<int>(rect.width());
  int height = static_cast<int>(rect.height());
  // the number of pixels, not bytes
  gl->pixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<int>(rowBytes / bytesPerPixel));
  gl->texSubImage2D(glTexture->target(), 0, x, y, width, height, textureFormat.externalFormat,
                    textureFormat.externalType, pixels);
  gl->pixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}

void GLCommandQueue::submit(std::shared_ptr<CommandBuffer>) {
  gpu->processUnreferencedResources();
  auto gl = gpu->functions();
  gl->flush();
  // Reset GL state every frame to avoid interference from external GL calls.
  gpu->resetGLState();
}

std::shared_ptr<Semaphore> GLCommandQueue::insertSemaphore() {
  auto gl = gpu->functions();
  auto glSync = gl->fenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
  if (glSync == nullptr) {
    return nullptr;
  }
  return gpu->makeResource<GLSemaphore>(glSync);
}

void GLCommandQueue::waitSemaphore(std::shared_ptr<Semaphore> semaphore) {
  if (semaphore == nullptr) {
    return;
  }
  auto gl = gpu->functions();
  auto glSync = std::static_pointer_cast<GLSemaphore>(semaphore)->glSync();
#if defined(__EMSCRIPTEN__)
  auto timeoutLo = static_cast<uint32_t>(GL_TIMEOUT_IGNORED & 0xFFFFFFFFull);
  auto timeoutHi = static_cast<uint32_t>(GL_TIMEOUT_IGNORED >> 32);
  gl->waitSync(glSync, 0, timeoutLo, timeoutHi);
#else
  gl->waitSync(glSync, 0, GL_TIMEOUT_IGNORED);
#endif
}

void GLCommandQueue::waitUntilCompleted() {
  auto gl = gpu->functions();
  gl->finish();
}

}  // namespace tgfx
